<?php
/*
* Класс воркер для базы данных.
* Полностью статичен
*
* Задача реализовать правильное рантайм кеширование объектов базы данных:
* 1. FetchObject
* 2. FetchManyObject
* 3. Creat new Object;
*
* А также очистить от лишнего класс core_Model
*/
class Worker {
	private static $_prefix = "new_tw";
	/*
	* райнтайм
	*/
	private static $_object_hash = array();
	private static $_query_cnt = 0;
	public static $_worker_stat = Array(
										"setRunTime"=>0,
										"getRunTime"=>0,
										"inMemCache"=>0,
										"inSQL"	=> 0,
										"inSQLSQL"	=> Array(),
										"inSQLOMany"	=> 0,
										"inSQLOManySQL"	=> Array(),
										"inSQLOManySub"	=> 0,
										"inSQLOManySubSQL"	=> Array()
									);
	/*Добавить в райнтайм кешь*/
    public static function setRunTime($key, $value) {
        #var_dump($value);
        #echo "В рантайм добавлен объект [$key]<br />";
        self::$_object_hash[$key] = $value;
        self::$_worker_stat["setRunTime"] ++;
    }
    /*Извлечь из райнтайма*/
    public static function getRunTime($key) {

        $result = false;
        if (isset(self::$_object_hash[$key])) $result = self::$_object_hash[$key];
        if($result){
        	self::$_worker_stat["getRunTime"] ++;
        }
       # echo "Извлечен райнтайм объект [$result]<br />";
        return $result;
    }
    public static function findRunTime($key) {
       # echo "Ищем райнтайм объект [$key]<br />";
        $result = false;
        if (isset(self::$_object_hash[$key])) $result = true;
        return $result;
    }
  	/*Удалить райнтайм*/
    public static function deleteRunTime($key) {
      unset(self::$_object_hash[$key]);
    }
	/*Распечать рантайм*/
    public static function dumpRunTime() {
      return var_dump(self::$_object_hash);
    }
    
    public static function flushRunTime(){
		self::$_object_hash = Array();		
	}



/**
* А теперь самое основное
*/

    /**
    * Фетчер одиночного объекта
    * @param string $object
    * @param Array $pKey
    *
    * @returns Object or Null
    */
    public static function fetchObject($object,	Array $pKey){
    	//var_dump($pKey);
    	// Проверяем коннекты
		//echo " - fetchObject<br />";
    	$cacheID  = self::genOID($object,$pKey);
    	// - Ищем в рантайм кеше
    	if(self::findRunTime($cacheID)){
    		# Возвращаем объект
    		//echo "[* $object] - Нашли в райнтайм кеше<br />";
    		return self::getRunTime($cacheID);

    	// - Ищем в мемкеше в котором лежат только данные
    	}elseif( $object::$__cached_table && ($row = @DB::getMCH()->get($cacheID)) !==false ){
    		//echo "[* $object] - Нашли в мемкеше<br />";
    		self::$_worker_stat["inMemCache"]++;
    		# Создаем объект
    		$obj = new $object();
    		$row = json_decode($row);
    		foreach($row as $key => $value){
                if(isset($object::$__fields[$key])){
                    $obj->$key =  $value;
                }
            }
    		$obj->_insert=false;
            $obj->_initialized=true;
            self::setRunTime($cacheID,$obj);
            return $obj;
    	}else{
    		//echo "[* $object] - Ищем в БД<br />";
    		// - имеет смысл искать в БД;
    		$query = new Query();
    		self::$_worker_stat["inSQL"]++;
    		foreach($pKey as $key => $val){
    			$query->field($key,$val);
    		}
    		$sql = "Select * FROM ". $object . $query->toString();
    		self::$_worker_stat["inSQLSQL"][] = Array($sql,$query->getValue());
            //echo $sql."<br />";
            /*var_dump($query->getValue());*/
            $sth = DB::getDBH()->prepare($sql);
            $sth->execute($query->getValue());
            $row = $sth->fetch(PDO::FETCH_ASSOC);
            # Попутно записываем в кешь
            if(is_array($row)){
            	
            	$obj = new $object();
                foreach($row as $key => $value){
                    if(isset($object::$__fields[$key])){
                    	$obj->$key =  $value;
               		}
                }
                
                $obj->_insert=false;
	            $obj->_initialized=true;
                $obj->saveToCache();
                self::setRunTime($cacheID,$obj);
                return $obj;
                #$this->clear();
            }else{
            	return null;
            }
    	}
    }

    /**
    * Получить массив объектов
    * Выборка из базы данных объектов по запросу query
    * @param String $object
    * @param Object $query
    *
    * @return Array
    */
		/*public static function fetchManyObject2($object, Query $query, $sort = null) {
		# Формируем запрос к базе данных
			$sql = "Select ".implode( ",",$object::$__pKey)." FROM ".$object." ".$query->toString();
		# Формируем Рантайм кешКей
			$sqlRuntimeKey = md5(self::$_prefix."::".$sql . implode($query->getValue(),"::"));
		# Массив результатов
			$result = Array();
		# Маркер удачно извлеченных данных
			$extracted = false;
			
		# Пытаемся найти в райнтайм кеше
		
		
		
			if(self::findRunTime($sqlRuntimeKey)){
		# Если нашли возвращаем результат
				$result = self::getRunTime($sqlRuntimeKey);
				
				$extracted = true;
			} 
		# Если не нашли результата в рантайме то пробуем найти в мемкеше
		#  Тут Важное обновление мы добавили в результата для кеша, версию тегов
		#  Азначит мы должны проверить версию тегов и если хоть один сброшен, то мы должны сделать выборку из базы данных
			if(!$extracted){
		# Если объекту разрешено кешировать множественные запросы и что то надейно в кеше
		#  Пробуем поработать с этим о_О
				if($object::$__cache_many && ($row = @DB::getMCH()->get($sqlRuntimeKey)) !== false){
		#  Счетчик статистики

					self::$_worker_stat["inMemCache"]++;
					//print_r($row);
		#  Проверка на пустой массив
					if($row != "[]"){
		#  раскодируем результат из кеша
						$res = json_decode( $row );
						
		#  Проверяем у него наличие поля tags и актуал тайм
						if( isset($res->tags) && isset( $res->actualTime ) ){
		#  А теперь начинается самое интересное
		#    Проверяем версию тегов и если хоть один тег различается то отправляем его на перекеширование
							if( Tagger::compare_tags($res->tags) ){
		# Приступаем к заполнению самих объектов
				# Приобразуем распакованный Json stdObject в Массив
								$res = Utils::_objectToArray( $res->CacheData );
				# Циклом бежим по нему
								if(!empty($res)){
									foreach($res as $key => $row){
						# Генерируем ключ отдельного объекта
										$key_cache = self::genOID($object, $row);
						# FetchObject'ом получаем объект 
										$new_object = self::fetchObject($object,$row);
										if($new_object != null){
						# Помещаем в массив результатов
											$result[] = $new_object;
										}
									}
						# Помещаем массив в райнтайм кешь, на случай если в системе возникнит подобный запрос
									if(count($result) > 0){
										self::setRunTime($sqlRuntimeKey, $result);
									}
								}
								# Помечаем что данные успешно извлечены
								$extracted = true;
							}
						}
					}
				}
			}
				
		# Если данные не были получены из мемкеша то тут надобно покапаться в базе данных
			if(!$extracted){
		# Статистика запросов
				self::$_worker_stat["inSQLOMany"]++;
				self::$_worker_stat["inSQLOManySQL"][]=Array( $object, $sql, $query->getValue());
		# Выполняем запрос
				$sth = DB::getDBH()->prepare($sql);
				$sth->execute( $query->getValue() );
		# Создаем вспомагательные массивы
			# Массив для помещения  в Мемкеш () назвал фигово его
				$runtimeArr = Array();
			# Список ID не найденных в кеше
				$noCached = Array();
			# Счетчик элементов
				$i = 0;
			# Массивом пробегаемся по результату запроса
				while($row = $sth->fetch(PDO::FETCH_ASSOC)){
				# Формируем КешID по которому будем искать объект
					$cacheID  = self::genOID($object,$row);
				#  Сохроняем в массив ключейц райнтайма
					$runtimeArr[$i] = $row;		        	
				#  Теперь ищем в рантайме объект
					if(self::findRunTime($cacheID)){
					#  Если нашли берем его и помещаем в массив результатов
						$result[$i] = self::getRunTime($cacheID);
				# Если в рантайме не нашли то ищем в мемкеше
					}elseif($object::$__cached_table && ($resMem = @DB::getMCH()->get($cacheID)) !==false){
				# Если нашли в мемкеше
					# Статистика	
						self::$_worker_stat["inMemCache"]++;
					# Создаем и заполняем наш объект	
						$obj = new $object();
					# разкладываем результата из мемкеша
						$resMem = json_decode($resMem);
					# Циклом заполняем объект
						foreach($resMem as $key => $value){
						#  Проверяем наличие поля в объекте
							if(isset($object::$__fields[$key])){
							# Есть, заполняем
								$obj->$key =  $value;
							}
						}
					# Помечаем объект 
						$obj->_insert=false;
						$obj->_initialized=true;
					# Добавляем в рантайм
						self::setRunTime($cacheID,$obj);
					# Добавляем в результат выборки
						$result[$i] = $obj;
					}else{
					#  Если в кешах не нашлось пишем в массив не найденнх ключей
						$noCached[$i] = $row;
					}
					$i++;
				}
				
				
		# Если массив не найденных ключей не пустой
				if(!empty($noCached)){
			#  Делаем повторный запрос
					$query2 = new Query();
			# Добавляем в запрос не найденные ключи
					foreach($noCached as $ob){
						foreach($ob as $key => $val){
							$query2->fieldIN($key,$val);
						}			    	
					}
			# Формируем SQL запрос
					$sub_sql = "Select * FROM ".$object." ".$query2->toString();
			# Статистика
					self::$_worker_stat["inSQLOManySub"]++;
					self::$_worker_stat["inSQLOManySubSQL"][]=Array($object,$sub_sql,$query2->getValue());
			# Выполняем запрос
					$sth = DB::getDBH()->prepare($sub_sql);
					$sth->execute($query2->getValue());
			# Циклом обрабатываем данные
					while($row = $sth->fetch(PDO::FETCH_ASSOC)){
						$i ++;
			# Формируем КешьID для того что бы поместить в кешь Объект
						$keys = Array();
						foreach($object::$__pKey as $field){
							$keys[$field] = $row[$field];
						}
			# Генерируем кешID
						$cacheID  = self::genOID($object,$keys);
			# Создаем и заполняем кешь
						$obj = new $object();
						foreach($row as $key => $value){
							if(isset($object::$__fields[$key])){
								$obj->$key =  $value;
							}
						}
			# Необходимые пометки объекту
						$obj->_insert=false;
						$obj->_initialized=true;
						$obj->saveToCache();
						self::setRunTime($cacheID,$obj);
			# Добавляем в массив результатов
						$result[] = $obj;
					}
				}

				
			# Сохраняем результат в рантайме
				self::setRunTime($sqlRuntimeKey,$result);
			# Формируем Массив для помещения в мемкешь
				$toMem = Array(
				# Данные по запросу
					"CacheData" => $runtimeArr,
				# Время помещения (Можно заменить на время протухания)
					"actualTime"=> time(),
				# Массив с тегами которые затрагивает этот запрос
					"tags" 		=> Tagger::get($object, $query)

				);
				if( empty($toMem["tags"]) && $object::$__cache_many){
					//file_put_contents('/home/manfredi/data/ava.b3/cron/ClanWar.log', $sql."\n", FILE_APPEND );
				}
			# Поместили все в кешь
				if(@DB::getMCH()->get($sqlRuntimeKey) !== false) {
					DB::getMCH()->replace($sqlRuntimeKey,json_encode($toMem),0, (3600 * 10));
				} else {
					@DB::getMCH()->set($sqlRuntimeKey,json_encode($toMem), 0, (3600 * 10));
				}
			}
		# Сортируем результат если в этом есть необходимость
			$result = self::sortManyByField($result, $sort);
		# Возвращаем массив объектов
			return $result;
		}*/

    
    /**
    * Получить массив объектов
    * Выборка из базы данных объектов по запросу query
    * @param String $object
    * @param Object $query
    *
    * @return Array
    */
		public static function fetchManyObject($object, Query $query, $sort = null) {
		# Формируем запрос к базе данных
			$sql = "Select ".implode( ",",$object::$__pKey)." FROM ".$object." ".$query->toString();
		# Формируем Рантайм кешКей
			$sqlRuntimeKey = md5(self::$_prefix."::".$sql . implode($query->getValue(),"::"));
		# Массив результатов
			$result = Array();
		# Маркер удачно извлеченных данных
			$extracted = false;
			$in_memcache = false;
			$tags = Tagger::get($object, $query);
			$runtimeArr = Array();
		# Пытаемся найти в райнтайм кеше
		
		
		
			if(self::findRunTime($sqlRuntimeKey)){
		# Если нашли возвращаем результат
				$result = self::getRunTime($sqlRuntimeKey);
				$in_memcache = true;
				$extracted = true;
			} 
		# Если не нашли результата в рантайме то пробуем найти в мемкеше
		#  Тут Важное обновление мы добавили в результата для кеша, версию тегов
		#  Азначит мы должны проверить версию тегов и если хоть один сброшен, то мы должны сделать выборку из базы данных
			//if(count($tags) == 0){
			//	echo "<pre>$object";
			//	print_r();
			//}
			if( !$extracted && (count($query->tagValue) == count($tags)) &&  $object::$__cached_table){
		# Если объекту разрешено кешировать множественные запросы и что то надейно в кеше
		#  Пробуем поработать с этим о_О
				if(/*$object::$__cache_many && */ ($row = @DB::getMCH()->get($sqlRuntimeKey)) !== false){
		#  Счетчик статистики
					//echo "$object надейн в кеше!<br />";
					self::$_worker_stat["inMemCache"]++;
					//print_r($row);
		#  Проверка на пустой массив
					if($row != "[]"){
						//echo "-- Результат есть в кеше <br />";
		#  раскодируем результат из кеша
						$res = json_decode( $row );
		#  Проверяем у него наличие поля tags и актуал тайм
						if( isset($res->tags) && isset( $res->actualTime ) && isset($res->CacheData) ){
							
		#  А теперь начинается самое интересное
		#    Проверяем версию тегов и если хоть один тег различается то отправляем его на перекеширование
							if( Tagger::compare_tags($res->tags, $tags ) ){
								//echo  "---- Версия тегов подходящая";
								$tags = $res->tags;
		# Приступаем к заполнению самих объектов
				# Приобразуем распакованный Json stdObject в Массив
								$rows = Utils::_objectToArray( $res->CacheData );
								$i = 0;
								if( count($rows) > 0){
									//echo  "------- Есть данные";
									while($row = array_shift($rows) ){
									# Формируем КешID по которому будем искать объект
										$cacheID  = self::genOID($object,$row);
									#  Сохроняем в массив ключейц райнтайма
										$runtimeArr[$i] = $row;		        	
									#  Теперь ищем в рантайме объект
										if(self::findRunTime($cacheID)){
										#  Если нашли берем его и помещаем в массив результатов
											$result[$i] = self::getRunTime($cacheID);
									# Если в рантайме не нашли то ищем в мемкеше
										}else{
									# Помещаем ключ в ненайденное для получения из мемкеша и не полученнеое из него же 
											$result_array[$cacheID] = $i ;
											$noCached[$cacheID] = $row;
										}
										$i++;
									}
									//echo "<pre>";
									//print_r($result_array);
									//print_r($noCached);
								}else{
									//echo "<pre>";
									//print_r($res);
									//echo "ROWS == NULL <br />";
								}
								# Помечаем что данные успешно извлечены
								$extracted = true;
								# Данные получены из мемкеша
								$in_memcache = true;
							}else{
								//echo "<pre>";
								//echo "ВЕРСИИ ТЕГОВ НЕ СОВПАЛИ<br />";
								
							}
						}else{
							//echo "Косяк точно тут<br />";
							//echo "<pre>";
							//print_r($res);
						}
						
					}else{
						//echo "Не вероятно но тут";
						//echo "<pre>";
						//print_r($res);
					}
					
				}else{
					//echo "В кеше ничего не нашлось<br />";
				}
			}
				
		# Если данные не были получены из мемкеша то тут надобно покапаться в базе данных
			if(!$extracted){
		# Статистика запросов
				self::$_worker_stat["inSQLOMany"]++;
				
		# Выполняем запрос
				$sth = DB::getDBH()->prepare($sql);
				$sth->execute( $query->getValue() );
		# Создаем вспомагательные массивы
			# Массив для помещения  в Мемкеш () назвал фигово его
				//$runtimeArr = Array();
			# Список ID не найденных в кеше
				$noCached = Array();
			# Счетчик элементов
				$i = 0;
			# Массивом пробегаемся по результату запроса
				$result_array = Array();
				while($row = $sth->fetch(PDO::FETCH_ASSOC)){
				# Формируем КешID по которому будем искать объект
					$cacheID  = self::genOID($object,$row);
				#  Сохроняем в массив ключейц райнтайма
					$runtimeArr[$i] = $row;		        	
				#  Теперь ищем в рантайме объект
					if(self::findRunTime($cacheID)){
					#  Если нашли берем его и помещаем в массив результатов
						$result[$i] = self::getRunTime($cacheID);
				# Если в рантайме не нашли то ищем в мемкеше
					}else{
				# Помещаем ключ в ненайденное для получения из мемкеша и не полученнеое из него же 
						$result_array[$cacheID] = $i ;
						$noCached[$cacheID] = $row;
					}
					$i++;
				}
				self::$_worker_stat["inSQLOManySQL"][] = Array( $object, $sql, $query->getValue(),$result_array, $noCached);
			}/**/
			
			
			# Пробуем получить пачку элементов из мемкеша
			# Если это необходимо
			if( !empty($result_array) && $object::$__cached_table ){
				//if( $in_memcache ){
				//	echo "Извлечение данных после мемкеша $object <br />";
				//}
			# Счетчик статистики
				self::$_worker_stat["inMemCache"]++;
			# Достаем из кеша
				$result_mc = @DB::getMCH()->get( array_keys($result_array) );
				
			# Массив расхождений из того что найдено в мемкеше и то что надо еще дополучить из БД
				$noCached = array_diff_key( $noCached, $result_mc );
				foreach($result_mc as $key => $row){
					$row = json_decode($row);
					$obj = new $object();
					foreach($row as $field => $value){
				#  Проверяем наличие поля в объекте
						if(isset($object::$__fields[$field])){
					# Есть, заполняем
							$obj->$field = $value;
						}
					}
				# Помечаем объект 
				 	//$obj->saveToCache();
					$obj->_insert=false;
					$obj->_initialized=true;
				# Добавляем в рантайм
					self::setRunTime($cacheID,$obj);
				# Добавляем в результат выборки
					$result[$result_array[$key]] = $obj;
				}
			}/**/
			
			
	# Если массив не найденных ключей не пустой
			if(!empty($noCached)){
				//print_r($noCached);
		#  Делаем повторный запрос
				$query2 = new Query();
		# Добавляем в запрос не найденные ключи
				foreach($noCached as $ob){
					foreach($ob as $key => $val){
						$query2->fieldIN($key,$val);
					}			    	
				}
		# Формируем SQL запрос
				$sub_sql = "Select * FROM ".$object." ".$query2->toString();
		# Статистика
				self::$_worker_stat["inSQLOManySub"]++;
				self::$_worker_stat["inSQLOManySubSQL"][]=Array($object,$sub_sql,$query2->getValue());
		# Выполняем запрос
				$sth = DB::getDBH()->prepare($sub_sql);
				$sth->execute($query2->getValue());
		# Циклом обрабатываем данные
				while($row = $sth->fetch(PDO::FETCH_ASSOC)){
					$i ++;
		# Формируем КешьID для того что бы поместить в кешь Объект
					$keys = Array();
					foreach($object::$__pKey as $field){
						$keys[$field] = $row[$field];
					}
		# Генерируем кешID
					$cacheID  = self::genOID($object,$keys);
		# Создаем и заполняем кешь
					$obj = new $object();
					foreach($row as $key => $value){
						if(isset($object::$__fields[$key])){
							$obj->$key =  $value;
						}
					}
		# Необходимые пометки объекту
					$obj->_insert=false;
					$obj->_initialized=true;
					$obj->saveToCache();
					self::setRunTime($cacheID,$obj);
		# Добавляем в массив результатов
					$result[] = $obj;
				}
			}/**/
			
			//echo "<pre>";
			//echo "$sqlRuntimeKey";
			//print_r($runtimeArr);
		# Сохраняем результат в рантайме
			self::setRunTime($sqlRuntimeKey,$result);
		# Формируем Массив для помещения в мемкешь
			$toMem = Array(
			# Данные по запросу
				"CacheData" => $runtimeArr,
			# Время помещения (Можно заменить на время протухания)
				"actualTime"=> time(),
			# Массив с тегами которые затрагивает этот запрос
				"tags" 		=> $tags

			);
		# Поместили все в кешь
			if(!$in_memcache){
				if(@DB::getMCH()->get($sqlRuntimeKey) !== false) {
					//echo "<pre>";
					//echo "Обновляем в КЕШЬ $object - $sqlRuntimeKey<br />";
					//print_r($toMem);
					DB::getMCH()->replace($sqlRuntimeKey,json_encode($toMem),0, (3600 * 10));
				} else {
					//echo "<pre>";
					//echo "Добавляем в КЕШЬ $object - $sqlRuntimeKey<br />";
					//print_r($toMem);
					@DB::getMCH()->set($sqlRuntimeKey,json_encode($toMem), 0, (3600 * 10));
				}
				//echo "runtimeArr NOT NULL $object <br />";
			}
			
		# Сортируем результат если в этом есть необходимость
			$result = self::sortManyByField($result, $sort, $query);
			#if(!empty($query->order)){
			#	$result = $query->sortByOrder($result);
			#}	
		# Возвращаем массив объектов
			return $result;
		}

    
    public static function fetchObjectByFields($object, query $query){
    	$obj = self::fetchManyObject($object, $query,null);
    	return $obj[0];
    }
    
    public static function deleteManyCache($key){
	   DB::getMCH()->delete($key);
    }
    

/**
* Утилиты для работы
*/
	/**
	* Генерирует уникальное имя для объекта
	* По названию объекта и уникальным идентификаторам
	* @param mixed $object
	* @param mixed $pkey
	*/
    public static function genOID($object, $pkey){
    	 $return =  self::$_prefix."::".$object.":";
    	 if(count($pkey) > 0){
    	 	ksort($pkey);
    	 	$return.= implode($pkey, "_");
    	 }else{
    	 	return false;
    	 }

    	 return md5($return);
    }

	/**
	* Генерирует строку для WHERE
	* @param Array $pkey
	*/
	public static function creatWhereWithPkey($pkey = array()){
		$cnt = count($pkey);
		$keymap ="";
		$i=0;
		if($cnt > 0){
	        foreach($pkey as $key => $val){
        		$i++;
        		$keymap.=" {$key} = :{$key}";
    			$keymap.=($i < $cnt) ? " AND" : "";
	        }
        	return $keymap;
		}else{
			return null;
		}
	}
	
	
	private static function sortManyByField($arrayObject, $key, Query $query) {
		if($key != null){
			//if($this->__fields[$key]){
			$newArray = Array();
			if(count($arrayObject) > 0){
				foreach($arrayObject as $k => $v) {
					$newArray[$v->$key] = $v;
				}
			}
			ksort($newArray);
			return $newArray;            
		}
		return $arrayObject;
	}

}





?>